
#include "Code.h"
#include <iostream>
#include <sstream>

namespace mjc {

	Code::Code(const Code& other)
		:pc(other.pc),
		mainPc(other.mainPc),
		dataSize(other.dataSize)
	{
		memcpy(buffer, other.buffer, bufferSize);
	}

	int Code::Put(int c)
	{
		if(pc < bufferSize)
		{
			buffer[pc++] = static_cast<char>(c&0xff);
			return 1;
		}

		++pc;
		return 0;
	}

	int Code::Put2(int c)
	{
		if(Put(c>>8))
			return Put(c);

		return 0;
	}

	int Code::Put2(int pos, int c)
	{
		int oldPc = pc;
		pc = pos;
		int r = Put2(c);
		pc = oldPc;
		return r;
	}

	int Code::Put4(int c)
	{
		if(Put2(c>>16))
			return Put2(c);

		return 0;
	}

	void Code::Load(Item& item)
	{
		switch(item.GetKind())
		{
		case Item::K_CONSTANT:
			{
				int value = item.GetValue();

				if(item.GetType().GetKind() == Type::K_CLASS)//constant class = null
					Put(C_CONST0);
				else if(0 <= value && value <= 5)
					Put(C_CONST0 + value);
				else if(value == -1)
					Put(C_CONST_M1);
				else
				{
					Put(C_CONST);
					Put4(value);
				}
			}
			break;

		case Item::K_STATIC:
			Put(C_GETSTATIC);
			Put2(item.GetAddress());
			break;

		case Item::K_LOCAL:
			if(0 <= item.GetAddress() && item.GetAddress() <= 3)
				Put(C_LOAD0 + item.GetAddress());
			else
			{
				Put(C_LOAD);
				Put(item.GetAddress());
			}
			break;

		case Item::K_FIELD: //assert: object base address is on the stack
			Put(C_GETFIELD);
			Put2(item.GetAddress());
			break;

		case Item::K_ELEMENT: //assert: array base address and index are on stack
			if(item.GetType().GetKind() == Type::K_CHAR)
				Put(C_BALOAD);
			else
				Put(C_ALOAD);
			break;
		}

		item.SetKind(Item::K_STACK);
	}

	void Code::Assign(Item& item0, Item& item1)
	{
		Load(item1);

		switch(item0.GetKind())//call Assign(item0) instead of this switch (?)
		{
		case Item::K_LOCAL:
			Put(C_STORE);
			Put(item0.GetAddress());
			break;

		case Item::K_STATIC:
			Put(C_PUTSTATIC);
			Put2(item0.GetAddress());
			break;

		case Item::K_FIELD:
			Put(C_PUTFIELD);
			Put2(item0.GetAddress());
			break;

		case Item::K_ELEMENT:
			Put(C_ASTORE);
			break;
		}
	}

	void Code::Assign(Item& item)
	{
		switch(item.GetKind())
		{
		case Item::K_LOCAL:
			Put(C_STORE);
			Put(item.GetAddress());
			break;

		case Item::K_STATIC:
			Put(C_PUTSTATIC);
			Put2(item.GetAddress());
			break;

		case Item::K_FIELD:
			Put(C_PUTFIELD);
			Put2(item.GetAddress());
			break;

		case Item::K_ELEMENT:
			Put(C_ASTORE);
			break;
		}
	}

	void Code::Jump(int address)
	{
		Put(C_JMP);
		Put2(address);
	}

	void Code::FalseJump(Parser::Op op, int address)
	{
		Parser::Op inv = Parser::OP_UNKNOWN;

		switch(op)
		{
		case Parser::OP_EQ:
			inv = Parser::OP_NE;
			break;

		case Parser::OP_NE:
			inv = Parser::OP_EQ;
			break;

		case Parser::OP_LT:
			inv = Parser::OP_GE;
			break;

		case Parser::OP_GE:
			inv = Parser::OP_LT;
			break;

		case Parser::OP_LE:
			inv = Parser::OP_GT;
			break;

		case Parser::OP_GT:
			inv = Parser::OP_LE;
			break;
		}

		Put(C_JEQ + inv);
		Put2(address);
	}

	void Code::Fixup(int address)
	{
		Put2(address, pc);
	}

	void Code::Write(std::ostream& os)
	{
		int codeSize = pc;
		Put(static_cast<int>('M'));
		Put(static_cast<int>('J'));
		Put4(codeSize);
		Put4(dataSize);
		Put4(mainPc);

		os.write(buffer + codeSize, pc - codeSize);//header
		os.write(buffer, codeSize);//code
	}

	void Code::PrintDecoded() const
	{
		struct Local //local functions
		{
			int current;

			void P(const std::wstring& s)
			{
				std::wcout << s << std::endl;
			}

			int Get(const char* code)
			{
				return static_cast<int>(code[current++] & 0xff);
			}

			int Get2(const char* code)
			{
				return (Get(code)*256 + Get(code)) & 0xffff;
			}

			int Get4(const char* code)
			{
				return (Get2(code)<<16) + (Get2(code) & 0xffff);
			}
		} local;

		int codeSize = pc;
		local.current = 0;

		while(local.current < codeSize)
		{
			std::wstringstream ss;

			switch(local.Get(buffer))
			{
			case C_LOAD:      
				ss << L"load " << local.Get(buffer);
				local.P(ss.str());
				break;
			case C_LOAD0:     local.P(L"load0"); break;
			case C_LOAD1:     local.P(L"load1"); break;
			case C_LOAD2:     local.P(L"load2"); break;
			case C_LOAD3:     local.P(L"load3"); break;
			case C_STORE:     
				ss << L"store " << local.Get(buffer);
				local.P(ss.str()); 
				break;
			case C_STORE0:    local.P(L"store0"); break;
			case C_STORE1:    local.P(L"store1"); break;
			case C_STORE2:    local.P(L"store2"); break;
			case C_STORE3:    local.P(L"store3"); break;
			case C_GETSTATIC: 
				ss << L"getstatic " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_PUTSTATIC: 
				ss << L"putstatic " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_GETFIELD:  
				ss <<L"getfield " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_PUTFIELD:  
				ss << L"putfield " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_CONST0:    local.P(L"const0"); break;
			case C_CONST1:    local.P(L"const1"); break;
			case C_CONST2:    local.P(L"const2"); break;
			case C_CONST3:    local.P(L"const3"); break;
			case C_CONST4:    local.P(L"const4"); break;
			case C_CONST5:    local.P(L"const5"); break;
			case C_CONST_M1:  local.P(L"const_m1"); break;
			case C_CONST:    
				ss << L"const " << local.Get4(buffer);
				local.P(ss.str()); 
				break;
			case C_ADD:       local.P(L"add"); break;
			case C_SUB:       local.P(L"sub"); break;
			case C_MUL:       local.P(L"mul"); break;
			case C_DIV:       local.P(L"div"); break;
			case C_REM:       local.P(L"rem"); break;
			case C_NEG:       local.P(L"neg"); break;
			case C_SHL:       local.P(L"shl"); break;
			case C_SHR:       local.P(L"shr"); break;
			case C_NEW:      
				ss << L"new " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_NEWARRAY:  
				ss << L"newarray " << local.Get(buffer);
				local.P(ss.str()); 
				break;
			case C_ALOAD:     local.P(L"aload"); break;
			case C_ASTORE:    local.P(L"astore"); break;
			case C_BALOAD:    local.P(L"baload"); break;
			case C_BASTORE:   local.P(L"bastore"); break;
			case C_ARRAYLENGTH: local.P(L"arraylength"); break;
			case C_POP:       local.P(L"pop"); break;
			case C_JMP:    
				ss << L"jmp " << local.Get2(buffer);
				local.P(ss.str());
				break;
			case C_JEQ:     
				ss << L"jeq " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_JNE:     
				ss << L"jne " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_JLT:     
				ss <<  L"jlt " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_JLE:      
				ss << L"jle " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_JGT:      
				ss << L"jgt " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_JGE:      
				ss << L"jge " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_CALL:      
				ss << L"call " << local.Get2(buffer);
				local.P(ss.str()); 
				break;
			case C_RETURN:   local.P(L"return"); break;
			case C_EXIT:      local.P(L"exit"); break;
			case C_READ:      local.P(L"read"); break;
			case C_PRINT:     local.P(L"print"); break;
			case C_BREAD:     local.P(L"bread"); break;
			case C_BPRINT:    local.P(L"bprint"); break;
			case C_TRAP:      
				ss << L"trap " << local.Get(buffer);
				local.P(ss.str()); 
				break;
			case C_ENTER:   
				ss << L"enter " << local.Get(buffer) << L" " << local.Get(buffer);
				local.P(ss.str()); 
				break;
			default:        
				local.P(L"-- error--"); 
				break;
			}
		}
	}
}